/* Copyright (c) 2009 SpringShowcase, Inc.
 * All right reserved.
 * http://code.google.com/p/springshowcase/
 * This software is the confidential and proprietary information of BeyondWeb
 * , Inc. You shall not disclose such Confidential Information and
 * shall use it only in accordance with the terms of the license agreement
 * you entered into with BeyondWeb.
 *
 * Revision History
 * Author              Date             Description
 * ------------------   --------------    ------------------
 *  taeki.kim           2010. 4. 2.        
 */
package org.springshowcase.core.commons.logs;

import java.util.List;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;

import org.apache.commons.collections.EnumerationUtils;
import org.apache.commons.lang.builder.ToStringBuilder;
import org.apache.commons.lang.exception.ExceptionUtils;
import org.apache.commons.lang.time.StopWatch;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springshowcase.core.commons.utils.BeansUtils;
import org.springshowcase.core.commons.utils.StringUtils;

/**
 * Service,DAO trace 로그를 기록 한다. Service,DAO 메서드 invoke시 넘어온 VO 객체 ,실행 시간등을 로그로
 * 남긴다.
 * 
 * @author kim.tae.ki
 * @version
 * @see This file contains the declarations of String class and its helper
 * classes.
 */
public class LoggingAspect {

	// ---------------------------------------------------------------------
	// Class Variable Declaration
	// ---------------------------------------------------------------------

	/** The Constant log. */
	private static final Logger logger = LoggerFactory
			.getLogger(LoggingAspect.class);

	private final static String SERVICE_POSTFIX = "SERVICE";

	private final static String DAO_POSTFIX = "DAO";

	private final static String FACADE_POSTFIX = "FACADE";

	private final static String REPOSITORY_POSTFIX = "REPOSITORY";

	private List<String> targetComponent;

	// ---------------------------------------------------------------------
	// Constructor Method Declaration
	// ---------------------------------------------------------------------
	// ---------------------------------------------------------------------
	// DI Method (setter,getter) Declaration
	// ---------------------------------------------------------------------
	/**
	 * @param targetComponent the targetComponent to set
	 */
	public void setTargetComponent(List<String> targetComponent) {
		this.targetComponent = targetComponent;
	}

	// ---------------------------------------------------------------------
	// this Method (public/private) Declaration
	// ---------------------------------------------------------------------

	/**
	 * AOP Round Type 메서드
	 * 
	 * @param pjp the pjp
	 * 
	 * @return the object
	 * 
	 * @throws Throwable the throwable
	 */
	public Object roundLog(ProceedingJoinPoint pjp) throws Throwable {
		StringBuffer sbAppender = new StringBuffer();
		StopWatch stopWatch = null;
		Object[] argus;
		String invokeClass = pjp.getTarget().getClass().getName();
		String symbol = getSymbolic(invokeClass);

		if (logger.isInfoEnabled()
				&& (this.targetComponent != null && targetComponent.size() > 0)) {
			stopWatch = new StopWatch();
			stopWatch.start();
			// 로그 적용 대상인 컴포넌트일 경우
			if (hasTartgetComponet(pjp.getTarget().getClass().getName())) {
				sbAppender.append("\n" + symbol + "\n");
			}
			sbAppender.append(
					" Invoke class : [" + pjp.getTarget().getClass() + "]\n")
					.append(
							" Invoke method : [" + pjp.getSignature().getName()
									+ "]\n");

			argus = pjp.getArgs();
			for (Object o : argus) {
				sbAppender.append(" binding data : [").append(
						BeansUtils.toStringBean(o)).append("]\n");
			}
			logger.info(sbAppender.toString());
		}
		Object object = pjp.proceed();

		if (logger.isInfoEnabled()
				&& (this.targetComponent != null && targetComponent.size() > 0)) {
			stopWatch.stop();
			sbAppender.setLength(0);
			sbAppender.append("\n " + symbol + " Exceute Time : ["
					+ stopWatch.getTime() + "]\n");
			logger.info(sbAppender.toString());
		}
		return object;
	}

	/**
	 * AOP Exception Type일때 사용
	 * 
	 * @param jp the jp
	 * @param exception the exception
	 */
	public void exceptionLog(JoinPoint jp, Throwable exception) {

		StringBuilder sbAppender = new StringBuilder();
		sbAppender.append("\nException inside Controller thrown!\n");
		sbAppender.append("- Controller Info:"
				+ jp.getSignature().getDeclaringTypeName() + "."
				+ jp.getSignature().getName());

		sbAppender.append(buildControllerInfo(jp,
				"\n- Controlller arguments start\n",
				"\n- Controlller arguments end"));
		sbAppender.append("\n- Exception message:" + exception.getMessage()
				+ "\n");
		sbAppender.append("\n- Exception StackTrace:\n"
				+ ExceptionUtils.getFullStackTrace(exception));

		if (logger.isWarnEnabled()) {
			logger.warn(sbAppender.toString());
		}
	}

	/**
	 * 컨트롤러 집입시 HttpServletReuqest 로그 패킷 작성
	 * 
	 * @param jp the jp
	 * @param startTitle the start title
	 * @param endTitle the end title
	 * 
	 * @return the string
	 */
	private String buildControllerInfo(JoinPoint jp, String startTitle,
			String endTitle) {
		StringBuilder sbAppend = new StringBuilder();

		sbAppend.append(startTitle);

		for (int argIdx = 0; argIdx < jp.getArgs().length; argIdx++) {
			sbAppend.append("  [arg" + argIdx + ": "
					+ getString(jp.getArgs()[argIdx]) + "]\n");
		}

		if (sbAppend.length() >= 1) {
			sbAppend.deleteCharAt(sbAppend.length() - 1);
		}
		sbAppend.append(endTitle);
		return sbAppend.toString();
	}

	/**
	 * 넘어온 VO 오브젝트 내용을 trace 로그로 남긴다.
	 * 
	 * @param obj the obj
	 * 
	 * @return the string
	 */
	private String getString(Object obj) {
		if (obj == null)
			return "null";
		StringBuilder sbAppender = new StringBuilder();
		if (obj instanceof HttpServletRequest) {
			HttpServletRequest req = (HttpServletRequest) obj;
			sbAppender.append("req:");
			sbAppender.append(req.getRequestURI());
			if (!StringUtils.isEmpty(req.getQueryString())) {
				sbAppender.append("?").append(req.getQueryString());
			}

		}
		else if (obj instanceof HttpSession) {
			HttpSession session = (HttpSession) obj;
			sbAppender.append("session:");
			for (Object attrName : EnumerationUtils.toList(session
					.getAttributeNames())) {
				sbAppender.append(attrName + ":"
						+ session.getAttribute((String) attrName));
			}
		}
		else {
			sbAppender.append("arg:" + ToStringBuilder.reflectionToString(obj));
		}
		return sbAppender.toString();
	}

	private boolean hasTartgetComponet(String component) {
		for (String str : this.targetComponent) {
			if (StringUtils.contains(component, str)) {
				return true;
			}
		}
		return false;
	}

	private String getSymbolic(String component) {
		String symbolic = "ⓒ";
		String upperComponent = StringUtils.trimToEmpty(component)
				.toUpperCase();

		if (StringUtils.contains(upperComponent, SERVICE_POSTFIX)) {
			return "ⓢ";
		}
		else if (StringUtils.contains(upperComponent, DAO_POSTFIX)) {
			return "ⓓ";
		}
		else if (StringUtils.contains(upperComponent, FACADE_POSTFIX)) {
			return "ⓕ";
		}
		else if (StringUtils.contains(upperComponent, REPOSITORY_POSTFIX)) {
			return "ⓡ";
		}

		return component;
	}

	// ---------------------------------------------------------------------
	// interface implement Method (public/private) Declaration
	// ---------------------------------------------------------------------
	// ---------------------------------------------------------------------
	// abstract implement Method (public/private) Declaration
	// ---------------------------------------------------------------------
	// ---------------------------------------------------------------------
	// abstract Method (public/private) Declaration
	// ---------------------------------------------------------------------

}
